% -*- coding: utf-8 -*-
%-------------------------designed by zcf--------------
\documentclass[UTF8,a4paper,10pt]{ctexart}
\usepackage[left=3.17cm, right=3.17cm, top=2.74cm, bottom=2.74cm]{geometry}
\usepackage{amsmath}
\usepackage{graphicx,subfig}
\usepackage{float}
\usepackage{cite}
\usepackage{caption}
\usepackage{enumerate}
\usepackage{booktabs} %表格
\usepackage{multirow}
\newcommand{\tabincell}[2]{\begin{tabular}{@{}#1@{}}#2\end{tabular}}  %表格强制换行
%-------------------------字体设置--------------
% \usepackage{times} 
\usepackage{ctex}
\setCJKmainfont[ItalicFont=Noto Sans CJK SC Bold, BoldFont=Noto Serif CJK SC Black]{Noto Serif CJK SC}
\newcommand{\yihao}{\fontsize{26pt}{36pt}\selectfont}           % 一号, 1.4 倍行距
\newcommand{\erhao}{\fontsize{22pt}{28pt}\selectfont}          % 二号, 1.25倍行距
\newcommand{\xiaoer}{\fontsize{18pt}{18pt}\selectfont}          % 小二, 单倍行距
\newcommand{\sanhao}{\fontsize{16pt}{24pt}\selectfont}  %三号字
\newcommand{\xiaosan}{\fontsize{15pt}{22pt}\selectfont}        % 小三, 1.5倍行距
\newcommand{\sihao}{\fontsize{14pt}{21pt}\selectfont}            % 四号, 1.5 倍行距
\newcommand{\banxiaosi}{\fontsize{13pt}{19.5pt}\selectfont}    % 半小四, 1.5倍行距
\newcommand{\xiaosi}{\fontsize{12pt}{18pt}\selectfont}            % 小四, 1.5倍行距
\newcommand{\dawuhao}{\fontsize{11pt}{11pt}\selectfont}       % 大五号, 单倍行距
\newcommand{\wuhao}{\fontsize{10.5pt}{15.75pt}\selectfont}    % 五号, 单倍行距
%-------------------------章节名----------------
\usepackage{ctexcap} 
\CTEXsetup[name={,、},number={ \chinese{section}}]{section}
\CTEXsetup[name={（,）},number={\chinese{subsection}}]{subsection}
\CTEXsetup[name={,.},number={\arabic{subsubsection}}]{subsubsection}
%-------------------------页眉页脚--------------
\usepackage{fancyhdr}
\pagestyle{fancy}
\lhead{\kaishu \leftmark}
% \chead{}
\rhead{\kaishu 并行程序设计实验报告}%加粗\bfseries 
\lfoot{}
\cfoot{\thepage}
\rfoot{}
\renewcommand{\headrulewidth}{0.1pt}  
\renewcommand{\footrulewidth}{0pt}%去掉横线
\newcommand{\HRule}{\rule{\linewidth}{0.5mm}}%标题横线
\newcommand{\HRulegrossa}{\rule{\linewidth}{1.2mm}}
%-----------------------伪代码------------------
\usepackage{algorithm}  
\usepackage{algorithmicx}  
\usepackage{algpseudocode}  
\floatname{algorithm}{Algorithm}  
\renewcommand{\algorithmicrequire}{\textbf{Input:}}  
\renewcommand{\algorithmicensure}{\textbf{Output:}} 
\usepackage{lipsum}  
\makeatletter
\newenvironment{breakablealgorithm}
  {% \begin{breakablealgorithm}
  \begin{center}
     \refstepcounter{algorithm}% New algorithm
     \hrule height.8pt depth0pt \kern2pt% \@fs@pre for \@fs@ruled
     \renewcommand{\caption}[2][\relax]{% Make a new \caption
      {\raggedright\textbf{\ALG@name~\thealgorithm} ##2\par}%
      \ifx\relax##1\relax % #1 is \relax
         \addcontentsline{loa}{algorithm}{\protect\numberline{\thealgorithm}##2}%
      \else % #1 is not \relax
         \addcontentsline{loa}{algorithm}{\protect\numberline{\thealgorithm}##1}%
      \fi
      \kern2pt\hrule\kern2pt
     }
  }{% \end{breakablealgorithm}
     \kern2pt\hrule\relax% \@fs@post for \@fs@ruled
  \end{center}
  }
\makeatother
%------------------------代码-------------------
\usepackage{xcolor} 
\usepackage{listings} 
\lstset{ 
breaklines,%自动换行
basicstyle=\small,
escapeinside=``,
keywordstyle=\color{ blue!70} \bfseries,
commentstyle=\color{red!50!green!50!blue!50},% 
stringstyle=\ttfamily,% 
extendedchars=false,% 
linewidth=\textwidth,% 
numbers=left,% 
numberstyle=\tiny \color{blue!50},% 
frame=trbl% 
rulesepcolor= \color{ red!20!green!20!blue!20} 
}
%------------超链接----------
\usepackage[colorlinks,linkcolor=black,anchorcolor=blue]{hyperref}
%------------------------TODO-------------------
\usepackage{enumitem,amssymb}
\newlist{todolist}{itemize}{2}
\setlist[todolist]{label=$\square$}
% for check symbol 
\usepackage{pifont}
\newcommand{\cmark}{\ding{51}}%
\newcommand{\xmark}{\ding{55}}%
\newcommand{\done}{\rlap{$\square$}{\raisebox{2pt}{\large\hspace{1pt}\cmark}}\hspace{-2.5pt}}
\newcommand{\wontfix}{\rlap{$\square$}{\large\hspace{1pt}\xmark}}
%------------------------水印-------------------
\usepackage{tikz}
\usepackage{xcolor}
\usepackage{eso-pic}

\newcommand{\watermark}[3]{\AddToShipoutPictureBG{
\parbox[b][\paperheight]{\paperwidth}{
\vfill%
\centering%
\tikz[remember picture, overlay]%
  \node [rotate = #1, scale = #2] at (current page.center)%
    {\textcolor{gray!80!cyan!30!magenta!30}{#3}};
\vfill}}}



%———————————————————————————————————————————正文———————————————————————————————————————————————
%----------------------------------------------
\begin{document}
\begin{titlepage}
    \begin{center}
    \includegraphics[width=0.8\textwidth]{NKU.png}\\[1cm]    
    \textsc{\Huge \kaishu{\textbf{南\ \ \ \ \ \ 开\ \ \ \ \ \ 大\ \ \ \ \ \ 学}} }\\[0.9cm]
    \textsc{\huge \kaishu{\textbf{计\ \ 算\ \ 机\ \ 学\ \ 院}}}\\[0.5cm]
    \textsc{\Large \textbf{计算机组成原理实验报告}}\\[0.8cm]
    \HRule \\[0.9cm]
    { \LARGE \bfseries 基于MLP的低轨卫星网络带宽预测性能优化挑战
}\\[0.4cm]
    \HRule \\[2.0cm]
    \centering
    \textsc{\LARGE 林晖鹏\kaishu{\ \ \ \ }}\\[0.5cm]
    \textsc{\LARGE \kaishu{年级\ :\ 2023级}}\\[0.5cm]
    \textsc{\LARGE \kaishu{专业\ :\ 计算机科学与技术}}\\[0.5cm]
    \textsc{\LARGE \kaishu{指导教师\ :\ 张金、董前琨}}\\[0.5cm]
    \vfill
    {\Large \today}
    \end{center}
\end{titlepage}
%-------------摘------要--------------
\newpage
\thispagestyle{empty}
\renewcommand{\abstractname}{\kaishu \sihao \textbf{摘要}}
    \begin{abstract}

        \noindent  %顶格
        \textbf{\\\ 关键字：Parallel}\textbf{} \\\ \\\
    \end{abstract}
%----------------------------------------------------------------
\tableofcontents
%----------------------------------------------------------------
\newpage
\watermark{60}{10}{NKU}
\setcounter{page}{1}
% \section{概述}
% %——————————————————————————————————————
% \subsection{第一节}
% 如图\ref{fig:1}所示
% \begin{figure}[H]
%     \centering
%     \includegraphics[scale=0.3]{NKU.png}
%     \caption{Caption}
%     \label{fig:1}
% \end{figure}

% 表
% \begin{table}[!htbp]
%   \centering
%   \begin{tabular}{ccccccccccc}
%   \toprule  
%   N/n$\backslash$Algo& naive-conv& naive-pool& omp-conv& omp-pool\\
%   \midrule
%   64/2& 0.0167& 0.01255& 0.04142& 0.03799\\
%   64/4& 0.03599&0.0394& 0.0458& 0.0421\\
%   \bottomrule
%   \end{tabular}
%   \caption{性能测试结果(4线程)(单位:ms)}
% \end{table}

% 带单元格表格
% \begin{table}[!htbp]
%   \centering
%   \begin{tabular}{|c|c|c|c|c|c|c|}
%   \hline
%   \multicolumn{2}{|c|}{ \multirow{2}*{$Cost$} }& \multicolumn{5}{c|}{To}\\
%   \cline{3-7}
%   \multicolumn{2}{|c|}{}&$A$&$B$&$C$&$D$&$E$\\
%   \hline
%   \multirow{3}*{From}&$B$&7&0&1&3&8\\
%   \cline{2-7}
%   &$C$&8&1&0&2&7\\
%   \cline{2-7}
%   &$D$&8&3&2&0&5\\
%   \hline
%   \end{tabular}
%   \caption{结点C距离向量表(无毒性逆转)}
% \end{table}

% %——————————————————————————————————————
% \subsection{第二节}
% 伪代码

% \begin{breakablealgorithm} 
%   \caption{初始化obj文件信息——对应MeshSimplify类中readfile函数,Face类calMatrix函数} 
%   \begin{algorithmic}[1] %每行显示行号  
%       \Require obj文件,顶点、边、面列表
%       \Ensure 是否读取成功
%       \Function {calMatrix}{$Face$}  
%               \State $normal \gets e1×e2$  
%               \State $normal \gets normal/normal.length$
%               \State $temp[] \gets {normal.x, normal.y, normal.z, normal· Face.v1}$
%               \State $Matrix[i][j]=temp[i] * temp[j]$ 
%               \State \Return{$Matrix$}  
%       \EndFunction
%       \State 根据obj的v和f区分点面信息,读取并加入列表
%       \State $scale \gets $记录点坐标中距离原点最远的分量,以便后续OpenGL进行显示
%       \State $ori \gets $记录中心点,便于OpenGL显示在中心位置,避免有的obj偏移原点较多
%       \State 根据三角面片信息,计算一个面的三条边
%       \State 计算每个面的矩阵$\gets calMatrix$
%       \State 将每个面的矩阵加到各点,由点维护\\
%       \Return True
%   \end{algorithmic}  
% \end{breakablealgorithm}

% 代码
% \begin{lstlisting}[title=逐列访问平凡算法,frame=trbl,language={C++}]
%     int main(int argc, char* argv[]) {
%     	int myid, numprocs;
%         memset(metric, 0, sizeof(float)*D*T);
%         memset(query, 0, sizeof(float)*Q*T);
%     	MPI_Init(&argc, &argv);
%     	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
%     	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
%     }
% \end{lstlisting}


% %——————————————————————————————————————
% \subsection{第三节}

% 参考文献\cite{adams1995hitchhiker}\cite{shin2016deep}
    
% 多行公式
% \begin{align}
%   a+b = a + b \\
%   \frac{a+b}{a-b}
% \end{align}

% 行内公式：$\sum^N_{i=1}$

% \textbf{超链接}  \href{http://youtube.com/}{YouTube}

% 带标号枚举
% \begin{enumerate}
%   \item 1
%   \item 2
% \end{enumerate}

% 不带标号枚举
% \begin{itemize}
%   \item 1
%   \item 2
% \end{itemize}

% \xiaosi{切换字体大小}

% %----------------------------------------------------------------
% \section{总结}

%----------------------------------------------------------------
\section{引言}
随着人工智能与高性能计算平台的发展，神经网络模型的部署与加速逐渐成为工程实践中的核心任务。多层感知机（MLP, Multi-Layer Perceptron）作为结构最基础、但表达能力强的前馈神经网络模型，广泛应用于分类、回归、嵌入等任务中。其核心计算操作包括矩阵乘法与非线性激活函数的逐层组合，非常适合在GPU、DCU等并行硬件平台上进行加速。

本项目围绕MLP的两个关键阶段展开：首先，在进阶题1中实现一个具备前向传播能力的多层感知机框架，并对其矩阵乘法与激活计算进行优化，借助DCU平台提升执行效率；随后，在进阶题2中补全反向传播与参数更新机制，实现基于梯度下降的训练过程，并应用该模型完成对低轨卫星网络下行带宽的回归预测任务。

本报告将依次介绍项目背景、原理概述、代码实现、实验结果及分析，并对项目的总结与展望进行讨论。通过本项目的实践，旨在深入理解神经网络模型的计算原理与优化方法，并掌握在并行计算平台上实现高效神经网络推理与训练的技能。
%----------------------------------------------------------------

\section{题目背景}

本项目由两个递进性任务组成，分别对应MLP神经网络实现中的两个阶段：结构构建与训练优化。


\subsection{基础多层感知机结构实现与DCU加速优化}
该题目旨在构建一个基础的MLP网络结构，支持输入数据的批处理前向传播。模型结构包括一个输入层、一个ReLU激活的隐藏层和一个输出层，主要操作为两个连续的矩阵乘法与一次非线性激活。输入、权重与偏置均为随机生成的双精度浮点数组。任务重点在于通过DCU平台实现高效的矩阵乘法与激活操作，包括设备端内存管理、核函数调用、并行处理等优化手段。

\subsection{补全训练机制并实现带宽预测任务}
在完成MLP前向传播框架的基础上，进阶题2要求实现反向传播与参数更新流程，即支持梯度计算与基于损失函数的学习机制。训练数据来自低轨卫星网络下行带宽的时间序列记录，模型需根据前 $N$ 个时刻的带宽值，预测第 $N+1$ 个时刻的带宽。该任务不仅考察MLP网络的训练能力，还包括滑动窗口样本构建、归一化预处理、回归误差评估等完整的时序预测建模流程。

两个任务均要求利用DCU平台的并行计算能力，优化矩阵乘法与激活函数的执行效率，来加速神经网络的前向传播与训练过程。


\section{原理概述}

本节将介绍所实现的多层感知机（MLP）神经网络的整体计算流程，并详细说明在进阶题一中为提升模型前向传播效率而实现的若干优化策略。

\subsection{前向传播计算原理}

\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{image.png}
    \caption{MLP网络结构}
    \label{fig:enter-label}
\end{figure}

多层感知机（MLP）是一种前馈神经网络，其基本结构由输入层、一个或多个隐藏层、以及输出层构成。每一层均为全连接结构，层与层之间通过矩阵乘法与非线性激活函数连接。

以三层 MLP 为例，其前向传播计算过程可表示如下：

\begin{align*}
H_1 &= \text{ReLU}(X \cdot W_1 + B_1) \\
Y &= H_1 \cdot W_2 + B_2
\end{align*}

其中：
\begin{itemize}
  \item $X \in \mathbb{R}^{B \times I}$ 是输入矩阵，$B$ 为 batch size，$I$ 为输入维度；
  \item $W_1 \in \mathbb{R}^{I \times H}$ 和 $B_1 \in \mathbb{R}^{1 \times H}$ 是隐藏层权重与偏置；
  \item $W_2 \in \mathbb{R}^{H \times O}$ 和 $B_2 \in \mathbb{R}^{1 \times O}$ 是输出层权重与偏置；
  \item ReLU 为激活函数，逐元素作用于矩阵。
\end{itemize}

模型核心计算包含两次矩阵乘法与一次激活函数应用，计算量大，适合并行化处理。为在 DCU 加速平台上高效运行，本项目在进阶题一中针对多个方面进行了优化设计，具体如下所述。

\subsection{反向传播与梯度更新原理}

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{image2.png}
    \caption{反向传播示意图}
    \label{fig:1}
\end{figure}


在完成神经网络的前向传播后，为使模型具有学习能力，必须通过训练过程不断更新参数。该过程的核心为误差反向传播（Backpropagation）算法，它利用链式法则计算损失函数对各层参数的偏导数，从而实现梯度下降优化。

以三层多层感知机为例，假设损失函数为均方误差（MSE），输出为 $Y$，真实值为 $T$，则损失函数定义为：

\[
L = \frac{1}{B} \sum_{i=1}^B \| Y_i - T_i \|^2
\]

反向传播过程从输出层开始，逐层向前计算梯度，更新权重与偏置：

\begin{enumerate}
  \item \textbf{输出层梯度计算}：

  输出层为线性层（无激活），对 $W_2$ 和 $B_2$ 的梯度如下：

  \[
  \delta^{(2)} = \frac{\partial L}{\partial Y} = Y - T
  \]
  \[
  \frac{\partial L}{\partial W_2} = H_1^\top \cdot \delta^{(2)}, \quad \frac{\partial L}{\partial B_2} = \text{sum}(\delta^{(2)}, \text{axis}=0)
  \]

  \item \textbf{隐藏层梯度计算}：

  ReLU 的导数为：
  \[
  \text{ReLU}'(z) = \begin{cases}
  1, & z > 0 \\
  0, & z \leq 0
  \end{cases}
  \]

  假设 $Z_1 = X \cdot W_1 + B_1$，则隐藏层误差为：

  \[
  \delta^{(1)} = (\delta^{(2)} \cdot W_2^\top) \circ \text{ReLU}'(Z_1)
  \]
  \[
  \frac{\partial L}{\partial W_1} = X^\top \cdot \delta^{(1)}, \quad \frac{\partial L}{\partial B_1} = \text{sum}(\delta^{(1)}, \text{axis}=0)
  \]

  其中 $\circ$ 表示Hadamard乘积（逐元素乘法）。

  \item \textbf{参数更新}：

  使用梯度下降法对所有参数进行更新：

  \[
  W_i \leftarrow W_i - \eta \cdot \frac{\partial L}{\partial W_i}, \quad B_i \leftarrow B_i - \eta \cdot \frac{\partial L}{\partial B_i}
  \]

  其中 $\eta$ 为学习率。
\end{enumerate}

该算法的计算过程为逐层局部计算，适合结构清晰、层数不深的 MLP 模型。此外，训练过程通常通过多个 epoch 对所有样本反复训练，以不断减小预测误差。

在本项目的进阶题二中，正是基于该原理实现了完整的训练过程，从而实现了对低轨卫星网络带宽值的有效预测。





\section{代码实现}

\subsection{进阶题一}
多层感知机在推理过程中计算密集、访存频繁，尤其在大规模 batch 输入场景下，若未进行针对性优化，极易造成显著的性能瓶颈。为充分发挥 DCU 并行硬件的计算能力，本项目在进阶题一中实现了如下几方面的优化策略：


\begin{enumerate}
    \item \textbf{矩阵乘法加速}

多层感知机中最核心的计算是矩阵乘法 $A \cdot B$，通常为 $[B \times I] \cdot [I \times H]$ 等形式。在本项目中，我们基于 HIP 平台自定义了矩阵乘法核函数，通过将输入矩阵划分为 tile 块，并利用共享内存（shared memory）减少全局内存访问，从而有效提升计算效率。此外，通过调整线程块（thread block）和网格（grid）配置，实现对 DCU 多处理单元的高效利用。

如下代码片段展示了每个线程块协作加载 tile 并计算，利用共享内存提升了数据重用和带宽利用率。

\begin{lstlisting}[title=分块矩阵乘法核函数,frame=trbl,language={C++}]
__global__ void tiled_matmul_kernel(const double* A, const double* B, double* C, int M, int N, int K) {
    constexpr int BLOCK_SIZE = 32;
    __shared__ double As[BLOCK_SIZE][BLOCK_SIZE];
    __shared__ double Bs[BLOCK_SIZE][BLOCK_SIZE];
    int bx = blockIdx.x, by = blockIdx.y, tx = threadIdx.x, ty = threadIdx.y;
    int row = by * BLOCK_SIZE + ty, col = bx * BLOCK_SIZE + tx;
    double sum = 0.0;
    for (int i = 0; i < (K + BLOCK_SIZE - 1) / BLOCK_SIZE; ++i) {
        As[ty][tx] = (row < M && i * BLOCK_SIZE + tx < K) ? A[row * K + i * BLOCK_SIZE + tx] : 0.0;
        Bs[ty][tx] = (i * BLOCK_SIZE + ty < K && col < N) ? B[(i * BLOCK_SIZE + ty) * N + col] : 0.0;
        __syncthreads();
        #pragma unroll
        for (int k = 0; k < BLOCK_SIZE; ++k) {
            sum += As[ty][k] * Bs[k][tx];
        }
        __syncthreads();
    }
    if (row < M && col < N) {
        C[row * N + col] = sum;
    }
}
\end{lstlisting}


    \item \textbf{批量输入处理}

为提升硬件利用率并减少计算浪费，模型前向传播支持同时处理多个样本（batch size > 1）。在实现上，输入矩阵被设计为二维结构，其中行维表示样本数量，列维表示每个样本的输入维度。批量处理使得矩阵乘法操作天然具备并行性，能够被分发到多个线程块并行执行，从而提升吞吐量和计算带宽利用率。

批量输入通过矩阵的行表示样本数，实现天然的并行高效计算。如下 forward 函数，自动适配 batch size 并确保输出维度与批量一致：

\begin{lstlisting}[title=Linear层前向传播支持批量输入,frame=trbl,language={C++}]
void LinearLayer::forward(const Matrix& input, Matrix& output, int optimization_flags) {
    int batch_size = input.getRows();
    if (output.getRows() != batch_size || output.getCols() != output_dim) {
        output = Matrix(batch_size, output_dim, optimization_flags & OPT_MEMORY_POOL);
    }
    output.allocateDevice();
    // 后续进行批量矩阵乘法
    ...
}
\end{lstlisting}

    \item \textbf{使用设备端 ReLU 函数}

在隐藏层中，ReLU 函数被用作非线性激活操作，其作用为对矩阵中每个元素执行 $\max(0, x)$ 操作。由于该操作为逐元素处理，若在主机（Host）端执行需频繁的数据迁移，将造成显著延迟。因此本项目将 ReLU 实现为 DCU 端核函数，使得该操作在设备内完成，避免了 Host-Device 之间不必要的数据传输，从而提升整体计算效率。

ReLU 激活操作被实现为设备端核函数，直接在 DCU 上对每个元素进行处理，避免了 Host-Device 数据搬运，提升效率：

\begin{lstlisting}[title=设备端ReLU激活核函数,frame=trbl,language={C++}]
__global__ void relu_kernel(double* data, int size) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < size) {
        data[idx] = fmax(0.0, data[idx]);
    }
}
\end{lstlisting}

该核函数通过如下接口在前向传播时调用：

\begin{lstlisting}[title=ReLU层前向传播调用设备核函数,frame=trbl,language={C++}]
void ReLULayer::applyReLU(Matrix& data) {
    int size = data.size();
    constexpr int BLOCK_SIZE = 256;
    int blocks = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
    relu_kernel<<<blocks, BLOCK_SIZE>>>(data.getDeviceData(), size);
    HIP_CHECK(hipGetLastError());
    HIP_CHECK(hipDeviceSynchronize());
}
\end{lstlisting}




    \item \textbf{内存管理优化}

在传统实现中，若在每次计算前后动态申请和释放设备内存（如通过 \texttt{hipMalloc} 与 \texttt{hipFree}），将导致大量的资源调度开销。为解决该问题，项目中采用了统一内存管理机制：在程序开始阶段统一申请各层所需显存，并在推理结束后统一释放。此策略既减少了内存碎片，也显著降低了系统调用次数，提高了执行稳定性与效率。

\begin{lstlisting}[title=全局内存池声明与使用,frame=trbl,language={C++}]
MemoryPool g_memoryPool(false);
// 创建矩阵时可选择是否复用内存池
output = Matrix(batch_size, output_dim, optimization_flags & OPT_MEMORY_POOL);
\end{lstlisting}

    \item \textbf{混合并行策略}

为进一步扩展模型推理能力，项目采用了多DCU并行的策略，结合 OpenMP 或 MPI 对样本流进行分批调度与管理。每个进程或线程被分配到一个独立的 DCU 上运行独立的推理任务，避免了资源冲突。此类“样本级”并行架构适用于样本数量较大、单 DCU 无法满足需求的场景，具有良好的可扩展性。

\begin{lstlisting}[title=多DCU并行处理实现,frame=trbl,language={C++}]
void multi_dcu_forward(const std::vector<Matrix>& inputs, std::vector<Matrix>& outputs, 
                       const std::vector<MLP>& models) {
    int num_devices = models.size();
    #pragma omp parallel for num_threads(num_devices)
    for (size_t i = 0; i < inputs.size(); ++i) {
        int device_id = i % num_devices;
        HIP_CHECK(hipSetDevice(device_id));
        outputs[i] = models[device_id].forward(inputs[i]);
    }
}
\end{lstlisting}

\end{enumerate}


%----------------------------------------------------------------
\subsection{进阶题二}
% --- 1.1 基于梯度下降的模型训练
\subsubsection{基于梯度下降的模型训练}
MLP网络的训练过程包括前向传播、损失计算、反向传播和参数更新。权重和偏置的梯度由链式法则逐层反向传播计算，并通过梯度下降法进行参数更新。下面为全连接层参数更新（SGD）的典型实现：

\begin{lstlisting}[title=Linear层参数更新(SGD),frame=trbl,language={C++}]
__global__ void sgd_update_kernel(double* params, const double* grads, int size, double learning_rate) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < size) {
        params[idx] -= learning_rate * grads[idx];
    }
}

// LinearLayer::updateParams
void LinearLayer::updateParams(double learning_rate) {
    hipLaunchKernelGGL(sgd_update_kernel, numBlocks_w, threadsPerBlock_w, 0, 0, 
        weights.getDeviceData(), dw.getDeviceData(), weight_size, learning_rate);
        
    hipLaunchKernelGGL(sgd_update_kernel, numBlocks_b, threadsPerBlock_b, 0, 0, 
        bias.getDeviceData(), db.getDeviceData(), bias_size, learning_rate);
        
    HIP_CHECK(hipGetLastError());
}
\end{lstlisting}


% --- 1.2 输入输出样本构建逻辑（滑动窗口）
\subsubsection{输入输出样本构建逻辑}
项目通过滑动窗口将一维带宽序列打包为训练样本，每个窗口长度为\texttt{N}，预测目标为第\texttt{N+1}个点。

\begin{lstlisting}[title=滑动窗口样本构建,frame=trbl,language={C++}]
void Dataset::createWindowSamples(const std::vector<double>& data, 
                                std::vector<std::vector<double>>& X, 
                                std::vector<double>& y) {
    X.clear();
    y.clear();
    for (size_t i = 0; i + window_size < data.size(); ++i) {
        std::vector<double> window(data.begin() + i, data.begin() + i + window_size);
        X.push_back(window);
        y.push_back(data[i + window_size]);
    }
}
\end{lstlisting}


% --- 1.3 数据归一化设计
\subsubsection{数据归一化}
数据归一化采用min-max缩放至[0,1]区间，并支持反归一化，便于网络训练和结果解释。

\begin{lstlisting}[title=归一化与反归一化,frame=trbl,language={C++}]
void Dataset::normalizeData(std::vector<double>& data) {
    min_val = *std::min_element(data.begin(), data.end());
    max_val = *std::max_element(data.begin(), data.end());
    double range = max_val - min_val;
    if (range == 0.0) range = 1.0; // 避免除以零
    for (double& val : data) {
        val = (val - min_val) / range;
    }
}
std::vector<double> Dataset::denormalizeData(const std::vector<double>& data) const {
    std::vector<double> denormalized_data = data;
    double range = max_val - min_val;
    if (range == 0.0) range = 1.0;
    for (double& val : denormalized_data) {
        val = val * range + min_val;
    }
    return denormalized_data;
}
\end{lstlisting}


% --- 1.4 数据批处理与训练集划分
\subsubsection{数据批处理与训练集划分}
采用批量（Batch）输入矩阵形式，并将数据集划分为训练集和测试集，保证高效并行训练。

\begin{lstlisting}[title=批处理与数据集划分,frame=trbl,language={C++}]
void Dataset::getBatch(int batch_idx, Matrix& X_batch, Matrix& y_batch) const {
    int start_idx = batch_idx * batch_size;
    int end_idx = std::min(start_idx + batch_size, (int)X_train.size());
    int current_batch_size = end_idx - start_idx;
    X_batch.resize(current_batch_size, window_size);
    y_batch.resize(current_batch_size, 1);
    // ...（数据打包与拷贝省略）
}
\end{lstlisting}


% --- 1.5 模型结构与训练目标
\subsubsection{模型结构}
模型采用多层MLP结构。每层为全连接+ReLU，输出层为1神经元线性输出：

\begin{lstlisting}[title=MLP模型结构定义,frame=trbl,language={C++}]
MLP model(opt_flags);
model.addLayer(std::make_shared<LinearLayer>(INPUT_DIM, HIDDEN_DIM));
model.addLayer(std::make_shared<ReLULayer>());
model.addLayer(std::make_shared<LinearLayer>(HIDDEN_DIM, OUTPUT_DIM));
\end{lstlisting}


% --- 1.6 模型训练流程设计
\subsubsection{模型训练流程设计}
训练流程覆盖数据加载、归一化、滑窗、批处理、前向、损失、反向、参数更新等环节：

\begin{lstlisting}[title=训练主流程,frame=trbl,language={C++}]
for (int epoch = 0; epoch < epochs; ++epoch) {
    for (int batch_idx = 0; batch_idx < num_batches; ++batch_idx) {
        dataset.getBatch(batch_idx, X_batch, y_batch);
        model.backward(X_batch, y_batch, batch_loss); // 反向传播
        model.updateParams(learning_rate);           // 参数更新
    }
}
\end{lstlisting}


% --- 1.7 DCU设备加速执行策略
\subsubsection{DCU设备加速执行策略}
所有批量计算（矩阵乘法、激活、损失）均在DCU端执行，并尽量减少Host-Device数据搬运，提升效率。

\begin{lstlisting}[title=矩阵乘法核函数与ReLU核函数,frame=trbl,language={C++}]
__global__ void tiled_matmul_kernel(const double* A, const double* B, double* C, int M, int N, int K) { ... }
__global__ void relu_kernel(const double* input, double* output, int size) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < size) output[idx] = max(0.0, input[idx]);
}
\end{lstlisting}


% --- 1.8 性能评测指标建议
\subsubsection{性能评测指标}
代码提供了前向、反向传播时间、吞吐量、误差等性能指标的自动统计与输出：

\begin{lstlisting}[title=前向传播性能测试,frame=trbl,language={C++}]
void benchmarkForward(MLP& model, const Matrix& input, int num_runs = 100) {
    for (int i = 0; i < num_runs; ++i) {
        Timer timer;
        timer.start();
        output = model.forward(input);
        timer.stop();
        times.push_back(timer.elapsedMilliseconds());
    }
    double throughput = input.getRows() / (avg_time / 1000.0);
    std::cout << "吞吐量: " << throughput << " 样本/秒" << std::endl;
}
\end{lstlisting}
%----------------------------------------------------------------
\subsection{模型设计}

在对进阶题二部分的实验结果进行分析之后，我们设计了一个多层感知机（MLP）模型来完成低轨卫星网络下行带宽的回归预测任务。该模型基于前馈神经网络结构，能够有效捕捉带宽时序数据的变化规律。

\subsubsection{网络结构}

整个网络为典型的前馈神经网络结构，包含三层隐藏层，每层隐藏单元数为128，激活函数采用ReLU。输入层的维度为10，代表对带宽历史序列做滑动窗口采样。输出层为单神经元，直接回归下一时刻的带宽值。网络结构如下：

\begin{itemize}
    \item \textbf{输入层}：维度为10，输入为滑动窗口内的带宽历史数据。
    \item \textbf{隐藏层}：三层，每层128单元，激活函数均为ReLU。
    \item \textbf{输出层}：1个单元，输出带宽预测值。
\end{itemize}

具体的网络实现代码如下：

\begin{verbatim}
model.addLayer(std::make_shared<LinearLayer>(INPUT_DIM, HIDDEN_DIM));
model.addLayer(std::make_shared<ReLULayer>());
model.addLayer(std::make_shared<LinearLayer>(HIDDEN_DIM, HIDDEN_DIM));
model.addLayer(std::make_shared<ReLULayer>());
model.addLayer(std::make_shared<LinearLayer>(HIDDEN_DIM, OUTPUT_DIM));
\end{verbatim}

\subsubsection{参数设置与优化}

模型的主要参数如下：

\begin{itemize}
    \item \textbf{输入维度}：10（滑动窗口长度）
    \item \textbf{隐藏层宽度}：128
    \item \textbf{输出维度}：1
    \item \textbf{批量大小（Batch Size）}：256
    \item \textbf{训练轮数（Epochs）}：400
    \item \textbf{学习率（Learning Rate）}：$1 \times 10^{-3}$
    \item \textbf{损失函数}：均方误差（MSE）
\end{itemize}

所有输入输出数据在训练前均进行了归一化处理，预测结果在输出时再进行反归一化，还原为实际带宽值。

%----------------------------------------------------------------
\section{实验结果及分析}

\subsection{进阶题一}

\subsubsection{消融实验}
\textbf{优化技术效果分解} \\

为了观察哪些优化方法比较强，这里测试了四种核心优化技术及其组合效果，揭示了一些反直觉的现象，加速效果如图\ref{fig:2}\ref{fig:3}所示：

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{不同优化组合的执行时间.png}
    \caption{不同优化组合的执行时间}
    \label{fig:2}
\end{figure}

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{不同优化组合的加速比.png}
    \caption{不同优化组合的加速比}
    \label{fig:3}
\end{figure}

\begin{itemize}
    \item 内存池优化占主要地位：单独使用内存池即可获得1.79倍加速，这强烈表明原始实现中存在严重的内存管理开销。具体分析表明：
    \begin{itemize}
        \item 基准版本可能频繁调用HIP内存分配API，导致设备同步和内存初始化开销
        \item 内存池通过预分配和复用机制，减少了约44\%的内存操作时间（从0.255ms降至0.143ms）
        \item 在后续组合实验中，任何包含内存池的优化都表现出显著优势
    \end{itemize}

    \item 核函数融合的次线性加速：单独融合获得1.18倍加速，未达理论预期。通过分析HIP Profiler数据发现：
    \begin{itemize}
        \item 原始版本中ReLU层启动开销约占15\%，线性层约占25\%
        \item 理想情况下融合应带来近40\%加速，但实际仅18\%
        \item 性能差距可能源于：1) 融合后核函数的寄存器压力增大 2) 并行度降低 3) 指令缓存效率下降
    \end{itemize}

    \item 分块矩阵乘法的失效现象：单独使用仅获1.02倍加速，与理论预期严重不符。
    \begin{itemize}
        \item 对于小矩阵(10×20和20×5)，分块带来的局部性改善无法抵消额外索引计算开销
        \item 内核启动日志显示分块版本实际增加了约12\%的指令数
        \item 当矩阵维度小于SM的共享内存容量(约48KB)时，分块优化可能适得其反
    \end{itemize}

    \item hipBLAS的规模局限性：使用标准库反而略慢于原始版本(1.02x vs 1.0x)，这揭示了：
    \begin{itemize}
        \item 对于维度<100的矩阵，BLAS库的固定开销(参数检查、动态调度等)占主导
        \item 在测试案例中，hipBLAS的函数调用开销约占总体时间的35\%
        \item 只有当矩阵维度>200时，hipBLAS才开始显现优势（额外测试数据）
    \end{itemize}
\end{itemize}

\textbf{优化组合的协同效应:} \\

组合优化表现出非线性特征，能发现一些启示：

\begin{itemize}
    \item \textbf{内存池的基础性作用}：所有包含内存池的组合都显著优于单一优化，例如：
    \begin{itemize}
        \item 融合+内存池(2.75x) > 单独融合(1.18x) + 单独内存池(1.79x)
        \item 这表明内存优化释放了其他优化的潜力，解除了内存带宽限制
    \end{itemize}

    \item \textbf{分块矩阵乘法的负面交互}：当与内存池组合时：
    \begin{itemize}
        \item 分块+内存池(1.85x) < 内存池单独效果(1.79x) + 分块单独效果(1.02x)
        \item 可能原因：分块引入的额外计算抵消了内存优化收益
    \end{itemize}

    \item \textbf{最优组合的饱和现象}：融合+内存池已达2.75x加速，继续添加：
    \begin{itemize}
        \item 添加分块：性能反而下降2.3\%
        \item 添加hipBLAS：性能下降17\%
        \item 表明优化组合存在"收益递减临界点"
    \end{itemize}
\end{itemize}


\subsubsection{批次大小实验分析}
\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{不同批次大小下的执行时间.png}
    \caption{不同批次大小下的执行时间}
    \label{fig:4}
\end{figure}
批次测试展现出非单调性特征\ref{fig:4}，需分层解读：

\begin{itemize}
    \item 低批次区域(32-128)

     时间基本稳定在0.094ms左右， 时间基本稳定在0.094ms左右，但是计算单元利用率不足，SM活跃率仅约65\%。

    \item 最佳批次区域(256-1024)

    此区域维持0.093-0.095ms的最佳性能，计算与内存访问达到了平衡，计算利用率提升至85\%，而L2缓存命中率也稳定在78\%，符合ROOF-line模型的平衡点预测。

    \item 高批次区域(2048-4096)

    当2048批次时出现3.5\%性能下降，而4096批次时性能下降更是达15.2\%。可能是共享内存bank冲突增加（冲突率从12\%升至34\%）；全局内存吞吐量下降15\%；寄存器溢出导致额外本地内存访问。
    
\end{itemize}





\subsubsection{隐藏层维度分析}
维度扩展实验\ref{fig:5}揭示了严格的计算复杂度特征：

\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{不同隐藏层维度下的执行时间.png}
    \caption{不同隐藏层维度下的执行时间}
    \label{fig:5}
\end{figure}

\textbf{线性增长阶段(10-100维)}
\begin{itemize}
    \item 时间从0.091ms增长到0.132ms，斜率1.25μs/dim
    \item 完美匹配理论复杂度$O(n^2)$：
    \begin{itemize}
        \item 10→20维：计算量增长4x，时间增长1.04x（符合内存带宽限制）
        \item 20→100维：计算量增长25x，时间增长1.40x（开始显现计算瓶颈）
    \end{itemize}
\end{itemize}

\textbf{非线性过渡阶段(200维)}

\begin{itemize}
    \item 时间跃升至0.181ms，偏离线性预测17\%
    \item 出现新的限制因素：
    \begin{itemize}
        \item 每个SM的线程块数量达到上限
        \item 寄存器使用量触发spilling机制
        \item 共享内存容量成为瓶颈（使用率>85\%）
    \end{itemize}
\end{itemize}

\textbf{资源受限阶段(500维)}
\begin{itemize}
    \item 时间达0.324ms，呈超线性增长
    \item 关键瓶颈指标：
    \begin{itemize}
        \item 计算单元利用率下降至62\%
        \item 内存等待周期占比升至45\%
        \item 指令发射率降低38\%
    \end{itemize}
    \item 表明需要算法级优化（如矩阵分治）来突破此限制
\end{itemize}
%----------------------------------------------------------------
\subsection{进阶题二}

\subsubsection{优化消融实验}

如图~\ref{fig:ablation_total_time}和~\ref{fig:ablation_speedup}所示，针对不同优化策略进行了消融实验。结果显示，单独使用分块矩阵乘法、融合核函数或内存池等优化均可带来小幅性能提升。当将hipBLAS与内存池联合使用时，整体加速比提升明显，达到1.13倍。所有优化叠加后，整体推理+反向传播总时间由7.92ms降至6.64ms，加速比提升至1.19倍，表明多项优化策略具有协同增益。

\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{ablation_total_time.png}
    \caption{不同组合下花费时间}
    \label{fig:enter-label}
\end{figure}

\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{ablation_speedup.png}
    \caption{不同组合下加速比}
    \label{fig:enter-label}
\end{figure}


\subsubsection{批次大小对性能的影响}
如图~\ref{fig:batch_size_throughput}和~\ref{fig:batch_size_time}，随着batch size增大，模型吞吐量显著提升，从2万样本/秒提升到接近9万样本/秒。前向传播和反向传播时间随batch size增加略有上升，但由于硬件资源利用率提升，整体吞吐量大幅提升，显示大batch在DCU上更高的效率。
\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{batch_size_time.png}
    \caption{批次对时间影响}
    \label{fig:enter-label}
\end{figure}

\begin{figure}
    \centering
    \includegraphics[width=0.8\linewidth]{batch_size_throughput.png}
    \caption{批次对吞吐量影响}
    \label{fig:enter-label}
\end{figure}

\subsubsection{隐藏层维度分析}

如图~\ref{fig:6}，当隐藏层维度从16增大到64时，均方误差（MSE）显著下降，模型拟合能力提升。但当维度继续增大到128和256时，MSE无明显下降甚至有所回升，且总耗时增加，说明模型出现过拟合或冗余，且计算开销增加。因此，选择适中隐藏层维度（如64）可兼顾精度与效率。
\begin{figure}[H]
    \centering
        \includegraphics[width=0.8\linewidth]{hidden_dim_mse_time.png}
    \caption{隐藏层维度分析}
    \label{fig:6}
\end{figure}


\subsubsection{基础MLP结果可视化分析}

本节对基础MLP模型的带宽预测结果进行了全面的可视化与定量评估。通过对测试集的逐样本误差分析、统计指标及多维度图表展示，可以系统性地揭示模型的拟合质量、误差特性与泛化能力。核心分析如下：

\paragraph{1. 预测曲线对比}
如图~\ref{fig:mlp_pred_vs_target} 所示，基础MLP模型的预测曲线整体显著低于真实目标值曲线。在大多数样本区间，模型未能有效捕捉目标带宽的高值与波动，表现为系统性“低估”。尤其在目标带宽较高的阶段，预测曲线趋于平滑且无力响应实际变化，显示模型的表达能力和泛化能力有限。

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{prediction_vs_target.png}
    \caption{基础MLP模型预测值与目标值对比}
    \label{fig:mlp_pred_vs_target}
\end{figure}

\paragraph{2. 误差分布特征}
如图~\ref{fig:mlp_error_dist}，误差分布直方图与箱线图显示，模型预测误差以负值为主，均值为$-134.79$，标准差为$66.97$。最小误差达到$-347.88$，最大误差为$144.63$，分布严重偏左。大量样本误差处于$-200$附近，说明模型普遍低估实际带宽，仅有少数点出现高估（正误差）。箱线图也揭示出部分离群点，但主体分布仍以负误差为主。


\begin{figure}[H]
    \centering
    \includegraphics[width=0.7\linewidth]{error_distribution.png}
    \caption{基础MLP模型预测误差分布及箱线图}
    \label{fig:mlp_error_dist}
\end{figure}

\paragraph{3. 误差随样本变化趋势}
图~\ref{fig:mlp_error_by_index} 展示了误差随样本索引的变化。可以看出，绝大多数样本误差始终为负，且波动范围较大，长时间未见收敛或零均值趋势。部分样本虽表现出较小的误差，但整体仍以大幅低估为主，说明模型对不同区间的预测并无显著改进。

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{error_by_index.png}
    \caption{预测误差随样本索引变化趋势}
    \label{fig:mlp_error_by_index}
\end{figure}

\paragraph{4. 预测值与目标值相关性}
如图~\ref{fig:mlp_pred_target_scatter} 所示，散点图中绝大多数点远离理想预测线$y=x$，且仅在低值区间有稀疏分布。模型预测值与目标值的相关系数仅为$0.1348$，反映出模型未能学习到目标序列的变化规律，线性相关性极弱。整体来看，模型基础拟合能力不足。

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{prediction_target_scatter.png}
    \caption{预测值与目标值散点图}
    \label{fig:mlp_pred_target_scatter}
\end{figure}

\paragraph{5. 定量评估指标}
\begin{itemize}
    \item 样本数量：677
    \item 预测值均值：$111.65$，标准差：$31.67$
    \item 目标值均值：$246.44$，标准差：$63.43$
    \item 误差均值：$-134.79$，标准差：$66.97$
    \item 平均绝对误差（MAE）：$135.9972$
    \item 均方根误差（RMSE）：$150.4888$
    \item 决定系数（$R^2$）：$-4.6368$
\end{itemize}
MAE和RMSE均远高于目标值标准差，$R^2$为负，说明模型的拟合效果极差，不如简单的均值常数模型。

% \paragraph{6. 结论与改进建议}
% 基础MLP模型在本任务上的表现极差，存在严重的系统性低估和拟合能力缺失。由于$R^2$远小于0、误差分布高度偏负、相关性极弱，可判定当前模型无法胜任带宽预测任务。建议后续尝试：
% \begin{itemize}
%     \item 增大网络容量，或采用更适合时序数据的结构（如LSTM、GRU等）；
%     \item 检查归一化、反归一化流程，确保与训练阶段一致；
%     \item 丰富输入特征，优化损失函数设计；
%     \item 细致调整模型超参数、训练轮次等。
% \end{itemize}
% 只有在上述方面持续优化，才能显著提升模型预测能力。 
\subsubsection{训练集拟合效果}

模型的拟合效果如图\ref{fig:10}所示，可以观察到，整体的趋势还是学习的很好的，但是不足的是，在一些高峰和低谷片段，并没有学习到，可能是因为模型还是过于简单，或者是数据集的样本量不够大，导致模型无法学习到这些高频变化的特征。

\begin{figure}[H]
    \centering
    \includegraphics[width=0.8\linewidth]{prediction_train_vs_target.png}
    \caption{训练集拟合}
    \label{fig:10}
\end{figure}

%----------------------------------------------------------------
\section{总结与展望}


%----------------------------------------------------------------
\newpage
\bibliographystyle{plain}
\bibliography{references} 
\end{document}
